package com.sun.impls;

import cn.hutool.core.util.StrUtil;
import com.sun.dto.BaseSearchDto;
import com.sun.dto.PagedList;
import com.sun.dto.users.UserSearchDto;
import com.sun.entity.users.User;
import com.sun.services.CustomPredicate;
import com.sun.utils.IdGenerator;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BaseService {
    @Autowired
    private IdGenerator idGenerator;
    @PersistenceContext
    private EntityManager em;

    public Long nextId() {
        return idGenerator.next();
    }


    //将分页方法抽象出来
    protected <T> PagedList<T> getPagedList(Class<T> obj, CustomPredicate<T> predicate, BaseSearchDto searchDto) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        //总条数
        int start = (searchDto.getPage() - 1) * searchDto.getSize();
        CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
        Root<T> countFrom = countQuery.from(obj);

        Predicate[] countParams = buildPredicates(builder, countFrom, predicate);
        countQuery.where(countParams);
        countQuery.select(builder.count(countFrom));
        int total = em.createQuery(countQuery).getSingleResult().intValue();
        CriteriaBuilder cb = em.getCriteriaBuilder();        //总条数

        //分页查询
        CriteriaQuery<T> criteriaQuery = cb.createQuery(obj);
        Root<T> from = criteriaQuery.from(obj);
        Predicate[] params = buildPredicates(cb, from, predicate);
        criteriaQuery = getCriteriaQuery(cb, from, criteriaQuery, searchDto);
        criteriaQuery.where(params);
        int totalPage = 0;
        if (total > 0) {
            totalPage = total % searchDto.getSize() == 0 ? total / searchDto.getSize() : total / searchDto.getSize() + 1;
        }

        List<T> selectResult = em.createQuery(criteriaQuery).setFirstResult(start).setMaxResults(searchDto.getSize()).getResultList();


        return new PagedList<>(selectResult, total, totalPage);
    }

    protected <T> List<T> getList(Class<T> obj, CustomPredicate<T> predicate, BaseSearchDto searchDto) {
        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();

        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(obj);

        Root<T> from = criteriaQuery.from(obj);
        List<Predicate> where = predicate.whereWarpper(criteriaBuilder, from);
        Predicate[] params = buildPredicates(criteriaBuilder, from, predicate);

        criteriaQuery = getCriteriaQuery(criteriaBuilder, from, criteriaQuery, searchDto);
        criteriaQuery.where(params);

        return em.createQuery(criteriaQuery).getResultList();
    }


    public <T> List<Predicate> basePredicates(CriteriaBuilder cb, Root<T> root, BaseSearchDto searchDto) {
        List<Predicate> predicates = new java.util.ArrayList<>();
        predicates.add(cb.equal(root.get("deleted"), 0L));
        if (!StrUtil.isEmpty(searchDto.getName())) {
            predicates.add(cb.equal(root.get("name"), searchDto.getName()));
        }
        if (!StrUtil.isEmpty(searchDto.getCode())) {
            predicates.add(cb.equal(root.get("code"), searchDto.getCode()));
        }
        if (!StrUtil.isEmpty(searchDto.getRemark())) {
            predicates.add(cb.equal(root.get("remark"), searchDto.getRemark()));
        }
        if (!StrUtil.isEmpty(searchDto.getDescription())) {
            predicates.add(cb.equal(root.get("description"), searchDto.getDescription()));
        }

        if (searchDto.getMinCreateTime() != null) {
            predicates.add(cb.greaterThanOrEqualTo(root.get("createTime"), searchDto.getMinCreateTime()));
        }
        if (searchDto.getMaxCreateTime() != null) {
            predicates.add(cb.lessThanOrEqualTo(root.get("createTime"), searchDto.getMaxCreateTime()));
        }
        if (searchDto.getMinUpdateTime() != null) {
            predicates.add(cb.greaterThanOrEqualTo(root.get("updateTime"), searchDto.getMinUpdateTime()));
        }
        if (searchDto.getMaxUpdateTime() != null) {
            predicates.add(cb.lessThanOrEqualTo(root.get("updateTime"), searchDto.getMaxUpdateTime()));
        }
        if (searchDto.getCreaterId() != null) {
            predicates.add(cb.equal(root.get("createrId"), searchDto.getCreaterId()));
        }
        if (searchDto.getUpdaterId() != null) {
            predicates.add(cb.equal(root.get("updaterId"), searchDto.getUpdaterId()));
        }
        if (!StrUtil.isEmpty(searchDto.getCreaterName())) {
            predicates.add(cb.equal(root.get("createrName"), searchDto.getCreaterName()));
        }
        if (!StrUtil.isEmpty(searchDto.getUpdaterName())) {
            predicates.add(cb.equal(root.get("updaterName"), searchDto.getUpdaterName()));
        }
        if (searchDto.getDeleted() != null) {
            predicates.add(cb.equal(root.get("deleted"), searchDto.getDeleted()));
        }
        return predicates;
    }

    public <T, E> CriteriaQuery<T> getCriteriaQuery(CriteriaBuilder cb, Root<E> root, CriteriaQuery<T> query, BaseSearchDto searchDto) {


        if (!StrUtil.isEmpty(searchDto.getOrderBy())) {
            if (searchDto.isAsc()) {
                query.orderBy(cb.asc(root.get(searchDto.getOrderBy())));
            } else {
                query.orderBy(cb.desc(root.get(searchDto.getOrderBy())));
            }
        } else {
            query.orderBy(cb.desc(root.get("createTime")));
        }

        return query;

    }

    /**
     * 构建 Predicate 数组
     *
     * @param cb        CriteriaBuilder 实例
     * @param root      Root 实例
     * @param predicate 自定义 Predicate 接口
     * @return Predicate 数组
     */
    private <T> Predicate[] buildPredicates(CriteriaBuilder cb, Root<T> root, CustomPredicate<T> predicate) {
        List<Predicate> predicates = predicate.whereWarpper(cb, root);
        return (predicates != null && !predicates.isEmpty()) ? predicates.toArray(new Predicate[0]) : new Predicate[0];
    }
}
